/*
 * Copyright (C) 2001-2012 Free Software Foundation, Inc.
 *
 * Author: Nikos Mavrogiannopoulos
 *
 * This file is part of GnuTLS.
 *
 * The GnuTLS is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

/* The certificate authentication functions which are needed in the handshake,
 * and are common to RSA and DHE key exchange, are in this file.
 */

#include <gnutls_int.h>
#include "gnutls_auth.h"
#include "gnutls_errors.h"
#include <auth/cert.h>
#include "gnutls_dh.h"
#include "gnutls_num.h"
#include "libtasn1.h"
#include "gnutls_datum.h"
#include "ext/signature.h"
#include <gnutls_pk.h>
#include <algorithms.h>
#include <gnutls_global.h>
#include <gnutls_record.h>
#include <gnutls_sig.h>
#include <gnutls_state.h>
#include <gnutls_pk.h>
#include <gnutls_x509.h>
#include <gnutls/abstract.h>
#include "debug.h"

#ifdef ENABLE_OPENPGP
#include "openpgp/gnutls_openpgp.h"

static gnutls_privkey_t alloc_and_load_pgp_key (const gnutls_openpgp_privkey_t
                                                key, int deinit);
static gnutls_pcert_st *alloc_and_load_pgp_certs (gnutls_openpgp_crt_t cert);

#endif

static gnutls_pcert_st *alloc_and_load_x509_certs (gnutls_x509_crt_t * certs,
                                                   unsigned);
static gnutls_privkey_t alloc_and_load_x509_key (gnutls_x509_privkey_t key,
                                                 int deinit);

#ifdef ENABLE_PKCS11
static gnutls_privkey_t alloc_and_load_pkcs11_key (gnutls_pkcs11_privkey_t
                                                   key, int deinit);
#endif

#define MAX_CLIENT_SIGN_ALGOS 3
#define CERTTYPE_SIZE (MAX_CLIENT_SIGN_ALGOS+1)
typedef enum CertificateSigType
{ RSA_SIGN = 1, DSA_SIGN = 2, ECDSA_SIGN = 64
} CertificateSigType;

/* Copies data from a internal certificate struct (gnutls_pcert_st) to 
 * exported certificate struct (cert_auth_info_t)
 */
static int
_gnutls_copy_certificate_auth_info (cert_auth_info_t info, gnutls_pcert_st * certs, size_t ncerts, int subkey_used,     /* openpgp only */
                                    void *keyid)
{
  /* Copy peer's information to auth_info_t
   */
  int ret;
  size_t i, j;

  if (info->raw_certificate_list != NULL)
    {
      for (j = 0; j < info->ncerts; j++)
        _gnutls_free_datum (&info->raw_certificate_list[j]);
      gnutls_free (info->raw_certificate_list);
    }

  if (ncerts == 0)
    {
      info->raw_certificate_list = NULL;
      info->ncerts = 0;
      return 0;
    }

  info->raw_certificate_list =
    gnutls_calloc (ncerts, sizeof (gnutls_datum_t));
  if (info->raw_certificate_list == NULL)
    {
      gnutls_assert ();
      return GNUTLS_E_MEMORY_ERROR;
    }

  for (i = 0; i < ncerts; i++)
    {
      if (certs[i].cert.size > 0)
        {
          ret =
            _gnutls_set_datum (&info->raw_certificate_list[i],
                               certs[i].cert.data, certs[i].cert.size);
          if (ret < 0)
            {
              gnutls_assert ();
              goto clear;
            }
        }
    }
  info->ncerts = ncerts;
  info->cert_type = certs[0].type;

#ifdef ENABLE_OPENPGP
  if (certs[0].type == GNUTLS_CRT_OPENPGP)
    {
      info->use_subkey = subkey_used;
      if (keyid)
        memcpy (info->subkey_id, keyid, GNUTLS_OPENPGP_KEYID_SIZE);
    }
#endif

  return 0;

clear:

  for (j = 0; j < i; j++)
    _gnutls_free_datum (&info->raw_certificate_list[j]);

  gnutls_free (info->raw_certificate_list);
  info->raw_certificate_list = NULL;

  return ret;
}




/* returns 0 if the algo_to-check exists in the pk_algos list,
 * -1 otherwise.
 */
inline static int
_gnutls_check_pk_algo_in_list (const gnutls_pk_algorithm_t *
                               pk_algos, int pk_algos_length,
                               gnutls_pk_algorithm_t algo_to_check)
{
  int i;
  for (i = 0; i < pk_algos_length; i++)
    {
      if (algo_to_check == pk_algos[i])
        {
          return 0;
        }
    }
  return -1;
}


/* Returns the issuer's Distinguished name in odn, of the certificate 
 * specified in cert.
 */
static int
_gnutls_cert_get_issuer_dn (gnutls_pcert_st * cert, gnutls_datum_t * odn)
{
  ASN1_TYPE dn;
  int len, result;
  int start, end;

  if ((result = asn1_create_element
       (_gnutls_get_pkix (), "PKIX1.Certificate", &dn)) != ASN1_SUCCESS)
    {
      gnutls_assert ();
      return _gnutls_asn2err (result);
    }

  result = asn1_der_decoding (&dn, cert->cert.data, cert->cert.size, NULL);
  if (result != ASN1_SUCCESS)
    {
      /* couldn't decode DER */
      gnutls_assert ();
      asn1_delete_structure (&dn);
      return _gnutls_asn2err (result);
    }

  result = asn1_der_decoding_startEnd (dn, cert->cert.data, cert->cert.size,
                                       "tbsCertificate.issuer", &start, &end);

  if (result != ASN1_SUCCESS)
    {
      /* couldn't decode DER */
      gnutls_assert ();
      asn1_delete_structure (&dn);
      return _gnutls_asn2err (result);
    }
  asn1_delete_structure (&dn);

  len = end - start + 1;

  odn->size = len;
  odn->data = &cert->cert.data[start];

  return 0;
}


/* Locates the most appropriate x509 certificate using the
 * given DN. If indx == -1 then no certificate was found.
 *
 * That is to guess which certificate to use, based on the 
 * CAs and sign algorithms supported by the peer server.
 */
static int
_find_x509_cert (const gnutls_certificate_credentials_t cred,
                 uint8_t * _data, size_t _data_size,
                 const gnutls_pk_algorithm_t * pk_algos,
                 int pk_algos_length, int *indx)
{
  unsigned size;
  gnutls_datum_t odn = { NULL, 0 };
  uint8_t *data = _data;
  ssize_t data_size = _data_size;
  unsigned i, j;
  int result, cert_pk;

  *indx = -1;

  /* If peer doesn't send any issuers and we have a single certificate
   * then send that one.
   */
  if (data_size == 0 && cred->ncerts == 1)
    {
      *indx = 0;
      return 0;
    }

  do
    {
      DECR_LENGTH_RET (data_size, 2, 0);
      size = _gnutls_read_uint16 (data);
      DECR_LENGTH_RET (data_size, size, 0);
      data += 2;

      for (i = 0; i < cred->ncerts; i++)
        {
          for (j = 0; j < cred->certs[i].cert_list_length; j++)
            {
              if ((result =
                   _gnutls_cert_get_issuer_dn (&cred->certs[i].cert_list[j],
                                               &odn)) < 0)
                {
                  gnutls_assert ();
                  return result;
                }

              if (odn.size != size)
                continue;

              /* If the DN matches and
               * the *_SIGN algorithm matches
               * the cert is our cert!
               */
              cert_pk =
                gnutls_pubkey_get_pk_algorithm (cred->certs[i].cert_list[0].pubkey,
                                                NULL);

              if ((memcmp (odn.data, data, size) == 0) &&
                  (_gnutls_check_pk_algo_in_list
                   (pk_algos, pk_algos_length, cert_pk) == 0))
                {
                  *indx = i;
                  break;
                }
            }
          if (*indx != -1)
            break;
        }

      if (*indx != -1)
        break;

      /* move to next record */
      data += size;
    }
  while (1);

  return 0;

}

#ifdef ENABLE_OPENPGP
/* Locates the most appropriate openpgp cert
 */
static int
_find_openpgp_cert (const gnutls_certificate_credentials_t cred,
                    gnutls_pk_algorithm_t * pk_algos,
                    int pk_algos_length, int *indx)
{
  unsigned i, j;

  *indx = -1;

  for (i = 0; i < cred->ncerts; i++)
    {
      for (j = 0; j < cred->certs[i].cert_list_length; j++)
        {

          /* If the *_SIGN algorithm matches
           * the cert is our cert!
           */
          if ((_gnutls_check_pk_algo_in_list
               (pk_algos, pk_algos_length,
                gnutls_pubkey_get_pk_algorithm (cred->certs[i].cert_list[0].pubkey,
                                                NULL)) == 0)
              && (cred->certs[i].cert_list[0].type == GNUTLS_CRT_OPENPGP))
            {
              *indx = i;
              break;
            }
        }
      if (*indx != -1)
        break;
    }

  return 0;
}
#endif

/* Returns the number of issuers in the server's
 * certificate request packet.
 */
static int
get_issuers_num (gnutls_session_t session, uint8_t * data, ssize_t data_size)
{
  int issuers_dn_len = 0, result;
  unsigned size;

  /* Count the number of the given issuers;
   * This is used to allocate the issuers_dn without
   * using realloc().
   */

  if (data_size == 0 || data == NULL)
    return 0;

  if (data_size > 0)
    do
      {
        /* This works like DECR_LEN() 
         */
        result = GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
        DECR_LENGTH_COM (data_size, 2, goto error);
        size = _gnutls_read_uint16 (data);

        result = GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
        DECR_LENGTH_COM (data_size, size, goto error);

        data += 2;

        if (size > 0)
          {
            issuers_dn_len++;
            data += size;
          }

        if (data_size == 0)
          break;

      }
    while (1);

  return issuers_dn_len;

error:
  return result;
}

/* Returns the issuers in the server's certificate request
 * packet.
 */
static int
get_issuers (gnutls_session_t session,
             gnutls_datum_t * issuers_dn, int issuers_len,
             uint8_t * data, size_t data_size)
{
  int i;
  unsigned size;

  if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509)
    return 0;

  /* put the requested DNs to req_dn, only in case
   * of X509 certificates.
   */
  if (issuers_len > 0)
    {

      for (i = 0; i < issuers_len; i++)
        {
          /* The checks here for the buffer boundaries
           * are not needed since the buffer has been
           * parsed above.
           */
          data_size -= 2;

          size = _gnutls_read_uint16 (data);

          data += 2;

          issuers_dn[i].data = data;
          issuers_dn[i].size = size;

          data += size;
        }
    }

  return 0;
}

static void
st_to_st2 (gnutls_retr2_st * st2, gnutls_retr_st * st)
{
  st2->cert_type = st->type;
  if (st->type == GNUTLS_CRT_OPENPGP)
    {
      st2->key_type = GNUTLS_PRIVKEY_OPENPGP;
    }
  else
    {
      st2->key_type = GNUTLS_PRIVKEY_X509;
    }
  st2->ncerts = st->ncerts;
  st2->deinit_all = st->deinit_all;

  switch (st2->cert_type)
    {
    case GNUTLS_CRT_OPENPGP:
      st2->cert.pgp = st->cert.pgp;
      st2->key.pgp = st->key.pgp;
      break;
    case GNUTLS_CRT_X509:
      st2->cert.x509 = st->cert.x509;
      st2->key.x509 = st->key.x509;
      break;
    default:
      return;
    }

}

/* Calls the client get callback.
 */
static int
call_get_cert_callback (gnutls_session_t session,
                        const gnutls_datum_t * issuers_dn,
                        int issuers_dn_length,
                        gnutls_pk_algorithm_t * pk_algos, int pk_algos_length)
{
  unsigned i;
  gnutls_pcert_st *local_certs = NULL;
  gnutls_privkey_t local_key = NULL;
  int ret = GNUTLS_E_INTERNAL_ERROR;
  gnutls_certificate_type_t type = gnutls_certificate_type_get (session);
  gnutls_certificate_credentials_t cred;
  gnutls_retr2_st st2;
  gnutls_pcert_st *pcert = NULL;
  unsigned int pcert_length = 0;

  cred = (gnutls_certificate_credentials_t)
    _gnutls_get_cred (session->key, GNUTLS_CRD_CERTIFICATE, NULL);
  if (cred == NULL)
    {
      gnutls_assert ();
      return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
    }

  memset (&st2, 0, sizeof (st2));

  if (cred->get_cert_callback2)
    {
      /* we avoid all allocations and transformations */
      ret = cred->get_cert_callback2 (session, issuers_dn, issuers_dn_length,
                                      pk_algos, pk_algos_length,
                                      &pcert, &pcert_length, &local_key);
      if (ret < 0)
        return gnutls_assert_val (GNUTLS_E_USER_ERROR);

      if (pcert_length > 0 && type != pcert[0].type)
        return gnutls_assert_val (GNUTLS_E_INVALID_REQUEST);

      if (pcert_length == 0)
        {
          pcert = NULL;
          local_key = NULL;
        }
      _gnutls_selected_certs_set (session, pcert, pcert_length, local_key, 0);

      return 0;

    }
  else if (cred->get_cert_callback)
    {
      ret = cred->get_cert_callback (session, issuers_dn, issuers_dn_length,
                                     pk_algos, pk_algos_length, &st2);

    }
  else
    {                           /* compatibility mode */
      gnutls_retr_st st;
      memset (&st, 0, sizeof (st));
      if (session->security_parameters.entity == GNUTLS_SERVER)
        {
          if (cred->server_get_cert_callback == NULL)
            {
              gnutls_assert ();
              return GNUTLS_E_INTERNAL_ERROR;
            }
          ret = cred->server_get_cert_callback (session, &st);
          if (ret >= 0)
            st_to_st2 (&st2, &st);
        }
      else
        {                       /* CLIENT */

          if (cred->client_get_cert_callback == NULL)
            {
              gnutls_assert ();
              return GNUTLS_E_INTERNAL_ERROR;
            }
          ret = cred->client_get_cert_callback (session,
                                                issuers_dn, issuers_dn_length,
                                                pk_algos, pk_algos_length,
                                                &st);
          if (ret >= 0)
            st_to_st2 (&st2, &st);
        }
    }

  if (ret < 0)
    {
      gnutls_assert ();
      return GNUTLS_E_USER_ERROR;
    }

  if (st2.ncerts == 0)
    return 0;                   /* no certificate was selected */

  if (type != st2.cert_type)
    {
      gnutls_assert ();
      ret = GNUTLS_E_INVALID_REQUEST;
      goto cleanup;
    }


  if (type == GNUTLS_CRT_X509)
    {
      local_certs = alloc_and_load_x509_certs (st2.cert.x509, st2.ncerts);
    }
  else
    {                           /* PGP */
      if (st2.ncerts > 1)
        {
          gnutls_assert ();
          ret = GNUTLS_E_INVALID_REQUEST;
          goto cleanup;
        }
#ifdef ENABLE_OPENPGP
      {
        local_certs = alloc_and_load_pgp_certs (st2.cert.pgp);
      }
#else
      ret = GNUTLS_E_UNIMPLEMENTED_FEATURE;
      goto cleanup;
#endif
    }

  if (local_certs == NULL)
    {
      gnutls_assert ();
      ret = GNUTLS_E_MEMORY_ERROR;
      goto cleanup;
    }

  switch (st2.key_type)
    {
    case GNUTLS_PRIVKEY_OPENPGP:
#ifdef ENABLE_OPENPGP
      if (st2.key.pgp != NULL)
        {
          local_key = alloc_and_load_pgp_key (st2.key.pgp, st2.deinit_all);
          if (local_key == NULL)
            {
              gnutls_assert ();
              ret = GNUTLS_E_INTERNAL_ERROR;
              goto cleanup;
            }
        }
#endif
      break;
    case GNUTLS_PRIVKEY_PKCS11:
#ifdef ENABLE_PKCS11
      if (st2.key.pkcs11 != NULL)
        {
          local_key =
            alloc_and_load_pkcs11_key (st2.key.pkcs11, st2.deinit_all);
          if (local_key == NULL)
            {
              gnutls_assert ();
              ret = GNUTLS_E_INTERNAL_ERROR;
              goto cleanup;
            }
        }
#endif
      break;
    case GNUTLS_PRIVKEY_X509:
      if (st2.key.x509 != NULL)
        {
          local_key = alloc_and_load_x509_key (st2.key.x509, st2.deinit_all);
          if (local_key == NULL)
            {
              gnutls_assert ();
              ret = GNUTLS_E_INTERNAL_ERROR;
              goto cleanup;
            }
        }
      break;
    default:
      gnutls_assert();
      ret = GNUTLS_E_INVALID_REQUEST;
      goto cleanup;
    }

  _gnutls_selected_certs_set (session, local_certs,
                              (local_certs != NULL) ? st2.ncerts : 0,
                              local_key, 1);

  ret = 0;

cleanup:

  if (st2.cert_type == GNUTLS_CRT_X509)
    {
      if (st2.deinit_all)
        {
          for (i = 0; i < st2.ncerts; i++)
            {
              gnutls_x509_crt_deinit (st2.cert.x509[i]);
            }
          gnutls_free(st2.cert.x509);
        }
    }
  else
    {
#ifdef ENABLE_OPENPGP
      if (st2.deinit_all)
        {
          gnutls_openpgp_crt_deinit (st2.cert.pgp);
        }
#endif
    }

  if (ret < 0)
    {
      if (local_key != NULL)
        gnutls_privkey_deinit (local_key);
    }

  return ret;
}

/* Finds the appropriate certificate depending on the cA Distinguished name
 * advertized by the server. If none matches then returns 0 and -1 as index.
 * In case of an error a negative error code, is returned.
 *
 * 20020128: added ability to select a certificate depending on the SIGN
 * algorithm (only in automatic mode).
 */
static int
_select_client_cert (gnutls_session_t session,
                     uint8_t * _data, size_t _data_size,
                     gnutls_pk_algorithm_t * pk_algos, int pk_algos_length)
{
  int result;
  int indx = -1;
  gnutls_certificate_credentials_t cred;
  uint8_t *data = _data;
  ssize_t data_size = _data_size;
  int issuers_dn_length;
  gnutls_datum_t *issuers_dn = NULL;

  cred = (gnutls_certificate_credentials_t)
    _gnutls_get_cred (session->key, GNUTLS_CRD_CERTIFICATE, NULL);
  if (cred == NULL)
    {
      gnutls_assert ();
      return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
    }

  if (cred->client_get_cert_callback != NULL
      || cred->get_cert_callback != NULL || cred->get_cert_callback2 != NULL)
    {

      /* use a callback to get certificate 
       */
      if (session->security_parameters.cert_type != GNUTLS_CRT_X509)
        issuers_dn_length = 0;
      else
        {
          issuers_dn_length = get_issuers_num (session, data, data_size);
          if (issuers_dn_length < 0)
            {
              gnutls_assert ();
              return issuers_dn_length;
            }

          if (issuers_dn_length > 0)
            {
              issuers_dn =
                gnutls_malloc (sizeof (gnutls_datum_t) * issuers_dn_length);
              if (issuers_dn == NULL)
                {
                  gnutls_assert ();
                  return GNUTLS_E_MEMORY_ERROR;
                }

              result =
                get_issuers (session, issuers_dn, issuers_dn_length,
                             data, data_size);
              if (result < 0)
                {
                  gnutls_assert ();
                  goto cleanup;
                }
            }
        }

      result =
        call_get_cert_callback (session, issuers_dn, issuers_dn_length,
                                pk_algos, pk_algos_length);
      goto cleanup;

    }
  else
    {
      /* If we have no callbacks, try to guess.
       */
      result = 0;

      if (session->security_parameters.cert_type == GNUTLS_CRT_X509)
        result =
          _find_x509_cert (cred, _data, _data_size,
                           pk_algos, pk_algos_length, &indx);
#ifdef ENABLE_OPENPGP
      else if (session->security_parameters.cert_type == GNUTLS_CRT_OPENPGP)
        result = _find_openpgp_cert (cred, pk_algos, pk_algos_length, &indx);
#endif

      if (result < 0)
        {
          gnutls_assert ();
          return result;
        }

      if (indx >= 0)
        {
          _gnutls_selected_certs_set (session,
                                      &cred->certs[indx].cert_list[0],
                                      cred->certs[indx].cert_list_length,
                                      cred->pkey[indx], 0);
        }
      else
        {
          _gnutls_selected_certs_set (session, NULL, 0, NULL, 0);
        }

      result = 0;
    }

cleanup:
  gnutls_free (issuers_dn);
  return result;

}

/* Generate certificate message
 */
static int
_gnutls_gen_x509_crt (gnutls_session_t session, gnutls_buffer_st * data)
{
  int ret, i;
  gnutls_pcert_st *apr_cert_list;
  gnutls_privkey_t apr_pkey;
  int apr_cert_list_length;

  /* find the appropriate certificate 
   */
  if ((ret =
       _gnutls_get_selected_cert (session, &apr_cert_list,
                                  &apr_cert_list_length, &apr_pkey)) < 0)
    {
      gnutls_assert ();
      return ret;
    }

  ret = 3;
  for (i = 0; i < apr_cert_list_length; i++)
    {
      ret += apr_cert_list[i].cert.size + 3;
      /* hold size
       * for uint24 */
    }

  /* if no certificates were found then send:
   * 0B 00 00 03 00 00 00    // Certificate with no certs
   * instead of:
   * 0B 00 00 00          // empty certificate handshake
   *
   * ( the above is the whole handshake message, not 
   * the one produced here )
   */

  ret = _gnutls_buffer_append_prefix (data, 24, ret - 3);
  if (ret < 0)
    return gnutls_assert_val (ret);

  for (i = 0; i < apr_cert_list_length; i++)
    {
      ret =
        _gnutls_buffer_append_data_prefix (data, 24,
                                           apr_cert_list[i].cert.data,
                                           apr_cert_list[i].cert.size);
      if (ret < 0)
        return gnutls_assert_val (ret);
    }

  return data->length;
}

enum PGPKeyDescriptorType
{ PGP_KEY_FINGERPRINT, PGP_KEY, PGP_KEY_SUBKEY, PGP_KEY_FINGERPRINT_SUBKEY };

#ifdef ENABLE_OPENPGP
static int
_gnutls_gen_openpgp_certificate (gnutls_session_t session,
                                 gnutls_buffer_st * data)
{
  int ret;
  gnutls_pcert_st *apr_cert_list;
  gnutls_privkey_t apr_pkey;
  int apr_cert_list_length;
  uint8_t type;
  uint8_t fpr[20];
  size_t fpr_size;
  unsigned int use_subkey = 0;

  /* find the appropriate certificate */
  if ((ret =
       _gnutls_get_selected_cert (session, &apr_cert_list,
                                  &apr_cert_list_length, &apr_pkey)) < 0)
    {
      gnutls_assert ();
      return ret;
    }

  ret = 3 + 1 + 3;



  if (apr_cert_list_length > 0)
    {
      fpr_size = sizeof (fpr);
      ret =
        gnutls_pubkey_get_openpgp_key_id (apr_cert_list[0].pubkey, 0, fpr,
                                          &fpr_size, &use_subkey);
      if (ret < 0)
        return gnutls_assert_val (ret);

      if (use_subkey != 0)
        ret += 1 + fpr_size;    /* for the keyid */

      ret += apr_cert_list[0].cert.size;
    }

  ret = _gnutls_buffer_append_prefix (data, 24, ret - 3);
  if (ret < 0)
    return gnutls_assert_val (ret);


  if (apr_cert_list_length > 0)
    {
      if (use_subkey != 0)
        {
          type = PGP_KEY_SUBKEY;

          ret = _gnutls_buffer_append_data (data, &type, 1);
          if (ret < 0)
            return gnutls_assert_val (ret);

          ret = _gnutls_buffer_append_data_prefix (data, 8, fpr, fpr_size);
          if (ret < 0)
            return gnutls_assert_val (ret);
        }
      else
        {
          type = PGP_KEY;
          ret = _gnutls_buffer_append_data (data, &type, 1);
          if (ret < 0)
            return gnutls_assert_val (ret);
        }

      ret =
        _gnutls_buffer_append_data_prefix (data, 24,
                                           apr_cert_list[0].cert.data,
                                           apr_cert_list[0].cert.size);
      if (ret < 0)
        return gnutls_assert_val (ret);
    }
  else                          /* empty - no certificate */
    {
      type = PGP_KEY;

      ret = _gnutls_buffer_append_data (data, &type, 1);
      if (ret < 0)
        return gnutls_assert_val (ret);

      ret = _gnutls_buffer_append_prefix (data, 24, 0);
      if (ret < 0)
        return gnutls_assert_val (ret);
    }

  return data->length;
}

static int
_gnutls_gen_openpgp_certificate_fpr (gnutls_session_t session,
                                     gnutls_buffer_st * data)
{
  int ret, packet_size;
  uint8_t type, fpr[20];
  size_t fpr_size;
  gnutls_pcert_st *apr_cert_list;
  gnutls_privkey_t apr_pkey;
  int apr_cert_list_length;
  unsigned int use_subkey = 0;

  /* find the appropriate certificate */
  if ((ret =
       _gnutls_get_selected_cert (session, &apr_cert_list,
                                  &apr_cert_list_length, &apr_pkey)) < 0)
    {
      gnutls_assert ();
      return ret;
    }

  fpr_size = sizeof (fpr);
  ret =
    gnutls_pubkey_get_openpgp_key_id (apr_cert_list[0].pubkey, 0, fpr,
                                      &fpr_size, &use_subkey);
  if (ret < 0)
    return gnutls_assert_val (ret);

  packet_size = 3 + 1;

  if (use_subkey)
    packet_size += 1 + fpr_size;        /* for the keyid */

  /* Only v4 fingerprints are sent 
   */
  if (apr_cert_list_length > 0)
    packet_size += 20 + 1;
  else                          /* empty certificate case */
    return _gnutls_gen_openpgp_certificate (session, data);

  ret = _gnutls_buffer_append_prefix (data, 24, packet_size - 3);
  if (ret < 0)
    return gnutls_assert_val (ret);

  if (use_subkey)
    {
      type = PGP_KEY_FINGERPRINT_SUBKEY;
      ret = _gnutls_buffer_append_data (data, &type, 1);
      if (ret < 0)
        return gnutls_assert_val (ret);

      ret = _gnutls_buffer_append_data_prefix (data, 8, fpr, fpr_size);
      if (ret < 0)
        return gnutls_assert_val (ret);
    }
  else
    {
      type = PGP_KEY_FINGERPRINT;       /* key fingerprint */
      ret = _gnutls_buffer_append_data (data, &type, 1);
      if (ret < 0)
        return gnutls_assert_val (ret);
    }

  fpr_size = sizeof (fpr);
  if ((ret =
       _gnutls_openpgp_fingerprint (&apr_cert_list[0].cert, fpr,
                                    &fpr_size)) < 0)
    {
      gnutls_assert ();
      return ret;
    }

  ret = _gnutls_buffer_append_data_prefix (data, 8, fpr, fpr_size);
  if (ret < 0)
    return gnutls_assert_val (ret);

  return data->length;
}
#endif


int
_gnutls_gen_cert_client_crt (gnutls_session_t session,
                                     gnutls_buffer_st * data)
{
  switch (session->security_parameters.cert_type)
    {
#ifdef ENABLE_OPENPGP
    case GNUTLS_CRT_OPENPGP:
      if (_gnutls_openpgp_send_fingerprint (session) == 0)
        return _gnutls_gen_openpgp_certificate (session, data);
      else
        return _gnutls_gen_openpgp_certificate_fpr (session, data);
#endif
    case GNUTLS_CRT_X509:
      return _gnutls_gen_x509_crt (session, data);

    default:
      gnutls_assert ();
      return GNUTLS_E_INTERNAL_ERROR;
    }
}

int
_gnutls_gen_cert_server_crt (gnutls_session_t session,
                                     gnutls_buffer_st * data)
{
  switch (session->security_parameters.cert_type)
    {
#ifdef ENABLE_OPENPGP
    case GNUTLS_CRT_OPENPGP:
      return _gnutls_gen_openpgp_certificate (session, data);
#endif
    case GNUTLS_CRT_X509:
      return _gnutls_gen_x509_crt (session, data);
    default:
      gnutls_assert ();
      return GNUTLS_E_INTERNAL_ERROR;
    }
}

/* Process server certificate
 */

#define CLEAR_CERTS for(x=0;x<peer_certificate_list_size;x++) gnutls_pcert_deinit(&peer_certificate_list[x])
static int
_gnutls_proc_x509_server_crt (gnutls_session_t session,
                                      uint8_t * data, size_t data_size)
{
  int size, len, ret;
  uint8_t *p = data;
  cert_auth_info_t info;
  gnutls_certificate_credentials_t cred;
  ssize_t dsize = data_size;
  int i;
  gnutls_pcert_st *peer_certificate_list;
  size_t peer_certificate_list_size = 0, j, x;
  gnutls_datum_t tmp;

  cred = (gnutls_certificate_credentials_t)
    _gnutls_get_cred (session->key, GNUTLS_CRD_CERTIFICATE, NULL);
  if (cred == NULL)
    {
      gnutls_assert ();
      return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
    }


  if ((ret =
       _gnutls_auth_info_set (session, GNUTLS_CRD_CERTIFICATE,
                              sizeof (cert_auth_info_st), 1)) < 0)
    {
      gnutls_assert ();
      return ret;
    }

  info = _gnutls_get_auth_info (session);

  if (data == NULL || data_size == 0)
    {
      gnutls_assert ();
      /* no certificate was sent */
      return GNUTLS_E_NO_CERTIFICATE_FOUND;
    }

  DECR_LEN (dsize, 3);
  size = _gnutls_read_uint24 (p);
  p += 3;

  /* some implementations send 0B 00 00 06 00 00 03 00 00 00
   * instead of just 0B 00 00 03 00 00 00 as an empty certificate message.
   */
  if (size == 0 || size == 3)
    {
      gnutls_assert ();
      /* no certificate was sent */
      return GNUTLS_E_NO_CERTIFICATE_FOUND;
    }

  i = dsize;
  while (i > 0)
    {
      DECR_LEN (dsize, 3);
      len = _gnutls_read_uint24 (p);
      p += 3;
      DECR_LEN (dsize, len);
      peer_certificate_list_size++;
      p += len;
      i -= len + 3;
    }

  if (peer_certificate_list_size == 0)
    {
      gnutls_assert ();
      return GNUTLS_E_NO_CERTIFICATE_FOUND;
    }

  /* Ok we now allocate the memory to hold the
   * certificate list 
   */

  peer_certificate_list =
    gnutls_calloc (1,
                   sizeof (gnutls_pcert_st) * (peer_certificate_list_size));
  if (peer_certificate_list == NULL)
    {
      gnutls_assert ();
      return GNUTLS_E_MEMORY_ERROR;
    }

  p = data + 3;

  /* Now we start parsing the list (again).
   * We don't use DECR_LEN since the list has
   * been parsed before.
   */

  for (j = 0; j < peer_certificate_list_size; j++)
    {
      len = _gnutls_read_uint24 (p);
      p += 3;

      tmp.size = len;
      tmp.data = p;

      ret =
        gnutls_pcert_import_x509_raw (&peer_certificate_list
                                      [j], &tmp, GNUTLS_X509_FMT_DER, 0);
      if (ret < 0)
        {
          gnutls_assert ();
          peer_certificate_list_size = j;
          goto cleanup;
        }

      p += len;
    }


  if ((ret =
       _gnutls_copy_certificate_auth_info (info,
                                           peer_certificate_list,
                                           peer_certificate_list_size, 0,
                                           NULL)) < 0)
    {
      gnutls_assert ();
      goto cleanup;
    }

  if ((ret =
       _gnutls_check_key_usage (&peer_certificate_list[0],
                                gnutls_kx_get (session))) < 0)
    {
      gnutls_assert ();
      goto cleanup;
    }

  ret = 0;

cleanup:
  CLEAR_CERTS;
  gnutls_free (peer_certificate_list);
  return ret;

}

#ifdef ENABLE_OPENPGP
static int
_gnutls_proc_openpgp_server_crt (gnutls_session_t session,
                                         uint8_t * data, size_t data_size)
{
  int size, ret, len;
  uint8_t *p = data;
  cert_auth_info_t info;
  gnutls_certificate_credentials_t cred;
  ssize_t dsize = data_size;
  int x, key_type;
  gnutls_pcert_st *peer_certificate_list = NULL;
  int peer_certificate_list_size = 0;
  gnutls_datum_t tmp, akey = { NULL, 0 };
  uint8_t subkey_id[GNUTLS_OPENPGP_KEYID_SIZE];
  unsigned int subkey_id_set = 0;

  cred = (gnutls_certificate_credentials_t)
    _gnutls_get_cred (session->key, GNUTLS_CRD_CERTIFICATE, NULL);
  if (cred == NULL)
    {
      gnutls_assert ();
      return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
    }

  if ((ret =
       _gnutls_auth_info_set (session, GNUTLS_CRD_CERTIFICATE,
                              sizeof (cert_auth_info_st), 1)) < 0)
    {
      gnutls_assert ();
      return ret;
    }

  info = _gnutls_get_auth_info (session);

  if (data == NULL || data_size == 0)
    {
      gnutls_assert ();
      return GNUTLS_E_NO_CERTIFICATE_FOUND;
    }

  DECR_LEN (dsize, 3);
  size = _gnutls_read_uint24 (p);
  p += 3;

  if (size == 0)
    {
      gnutls_assert ();
      /* no certificate was sent */
      return GNUTLS_E_NO_CERTIFICATE_FOUND;
    }

  /* Read PGPKeyDescriptor */
  DECR_LEN (dsize, 1);
  key_type = *p;
  p++;

  /* Try to read the keyid if present */
  if (key_type == PGP_KEY_FINGERPRINT_SUBKEY || key_type == PGP_KEY_SUBKEY)
    {
      /* check size */
      if (*p != GNUTLS_OPENPGP_KEYID_SIZE)
        {
          gnutls_assert ();
          return GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE;
        }

      DECR_LEN (dsize, 1);
      p++;

      DECR_LEN (dsize, GNUTLS_OPENPGP_KEYID_SIZE);
      memcpy (subkey_id, p, GNUTLS_OPENPGP_KEYID_SIZE);
      p += GNUTLS_OPENPGP_KEYID_SIZE;

      subkey_id_set = 1;

    }

  /* read the actual key or fingerprint */
  if (key_type == PGP_KEY_FINGERPRINT
      || key_type == PGP_KEY_FINGERPRINT_SUBKEY)
    {                           /* the fingerprint */

      DECR_LEN (dsize, 1);
      len = (uint8_t) * p;
      p++;

      if (len != 20)
        {
          gnutls_assert ();
          return GNUTLS_E_OPENPGP_FINGERPRINT_UNSUPPORTED;
        }

      DECR_LEN (dsize, 20);

      /* request the actual key from our database, or
       * a key server or anything.
       */
      if ((ret =
           _gnutls_openpgp_request_key (session, &akey, cred, p, 20)) < 0)
        {
          gnutls_assert ();
          return ret;
        }
      tmp = akey;
      peer_certificate_list_size++;

    }
  else if (key_type == PGP_KEY || key_type == PGP_KEY_SUBKEY)
    {                           /* the whole key */

      /* Read the actual certificate */
      DECR_LEN (dsize, 3);
      len = _gnutls_read_uint24 (p);
      p += 3;

      if (len == 0)
        {
          gnutls_assert ();
          /* no certificate was sent */
          return GNUTLS_E_NO_CERTIFICATE_FOUND;
        }

      DECR_LEN (dsize, len);
      peer_certificate_list_size++;

      tmp.size = len;
      tmp.data = p;

    }
  else
    {
      gnutls_assert ();
      return GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE;
    }

  /* ok we now have the peer's key in tmp datum
   */

  if (peer_certificate_list_size == 0)
    {
      gnutls_assert ();
      return GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
    }

  peer_certificate_list =
    gnutls_calloc (1,
                   sizeof (gnutls_pcert_st) * (peer_certificate_list_size));
  if (peer_certificate_list == NULL)
    {
      gnutls_assert ();
      ret = GNUTLS_E_MEMORY_ERROR;
      goto cleanup;
    }

  ret =
    gnutls_pcert_import_openpgp_raw (&peer_certificate_list[0],
                                     &tmp,
                                     GNUTLS_OPENPGP_FMT_RAW,
                                     (subkey_id_set != 0) ? subkey_id : NULL,
                                     0);
  if (ret < 0)
    {
      gnutls_assert ();
      goto cleanup;
    }

  ret =
    _gnutls_copy_certificate_auth_info (info,
                                        peer_certificate_list,
                                        peer_certificate_list_size,
                                        subkey_id_set,
                                        (subkey_id_set !=
                                         0) ? subkey_id : NULL);
  if (ret < 0)
    {
      gnutls_assert ();
      goto cleanup;
    }

  if ((ret =
       _gnutls_check_key_usage (&peer_certificate_list[0],
                                gnutls_kx_get (session))) < 0)
    {
      gnutls_assert ();
      goto cleanup;
    }

  ret = 0;

cleanup:

  _gnutls_free_datum (&akey);
  CLEAR_CERTS;
  gnutls_free (peer_certificate_list);
  return ret;

}
#endif

int
_gnutls_proc_crt (gnutls_session_t session,
                                      uint8_t * data, size_t data_size)
{
  int ret;
  gnutls_certificate_credentials_t cred;

  cred =
    (gnutls_certificate_credentials_t) _gnutls_get_cred (session->key,
                                                         GNUTLS_CRD_CERTIFICATE,
                                                         NULL);
  if (cred == NULL)
    {
      gnutls_assert ();
      return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
    }

  switch (session->security_parameters.cert_type)
    {
#ifdef ENABLE_OPENPGP
    case GNUTLS_CRT_OPENPGP:
      ret = _gnutls_proc_openpgp_server_crt (session,
                                                     data, data_size);
      break;
#endif
    case GNUTLS_CRT_X509:
      ret = _gnutls_proc_x509_server_crt (session, data, data_size);
      break;
    default:
      gnutls_assert ();
      return GNUTLS_E_INTERNAL_ERROR;
    }

  if (ret == 0 && cred->verify_callback != NULL)
    {
      ret = cred->verify_callback (session);
      if (ret != 0)
        ret = GNUTLS_E_CERTIFICATE_ERROR;
    }

  return ret;
}


/* Checks if we support the given signature algorithm 
 * (RSA or DSA). Returns the corresponding gnutls_pk_algorithm_t
 * if true;
 */
inline static int
_gnutls_check_supported_sign_algo (CertificateSigType algo)
{
  switch (algo)
    {
    case RSA_SIGN:
      return GNUTLS_PK_RSA;
    case DSA_SIGN:
      return GNUTLS_PK_DSA;
    case ECDSA_SIGN:
      return GNUTLS_PK_EC;
    }

  return -1;
}

int
_gnutls_proc_cert_cert_req (gnutls_session_t session, uint8_t * data,
                            size_t data_size)
{
  int size, ret;
  uint8_t *p;
  gnutls_certificate_credentials_t cred;
  ssize_t dsize;
  int i;
  gnutls_pk_algorithm_t pk_algos[MAX_CLIENT_SIGN_ALGOS];
  int pk_algos_length;
  gnutls_protocol_t ver = gnutls_protocol_get_version (session);

  cred = (gnutls_certificate_credentials_t)
    _gnutls_get_cred (session->key, GNUTLS_CRD_CERTIFICATE, NULL);
  if (cred == NULL)
    {
      gnutls_assert ();
      return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
    }

  if ((ret =
       _gnutls_auth_info_set (session, GNUTLS_CRD_CERTIFICATE,
                              sizeof (cert_auth_info_st), 0)) < 0)
    {
      gnutls_assert ();
      return ret;
    }

  p = data;
  dsize = data_size;

  DECR_LEN (dsize, 1);
  size = p[0];
  p++;
  /* check if the sign algorithm is supported.
   */
  pk_algos_length = 0;
  for (i = 0; i < size; i++, p++)
    {
      DECR_LEN (dsize, 1);
      if ((ret = _gnutls_check_supported_sign_algo (*p)) > 0)
        {
          if (pk_algos_length < MAX_CLIENT_SIGN_ALGOS)
            {
              pk_algos[pk_algos_length++] = ret;
            }
        }
    }

  if (pk_algos_length == 0)
    {
      gnutls_assert ();
      return GNUTLS_E_UNKNOWN_PK_ALGORITHM;
    }

  if (_gnutls_version_has_selectable_sighash (ver))
    {
      /* read supported hashes */
      int hash_num;
      DECR_LEN (dsize, 2);
      hash_num = _gnutls_read_uint16 (p);
      p += 2;
      DECR_LEN (dsize, hash_num);

      ret = _gnutls_sign_algorithm_parse_data (session, p, hash_num);
      if (ret < 0)
        {
          gnutls_assert ();
          return ret;
        }

      p += hash_num;
    }

  /* read the certificate authorities */
  DECR_LEN (dsize, 2);
  size = _gnutls_read_uint16 (p);
  p += 2;

  if (session->security_parameters.cert_type == GNUTLS_CRT_OPENPGP
      && size != 0)
    {
      gnutls_assert ();
      return GNUTLS_E_UNEXPECTED_PACKET_LENGTH;
    }

  DECR_LEN (dsize, size);

  /* now we ask the user to tell which one
   * he wants to use.
   */
  if ((ret =
       _select_client_cert (session, p, size, pk_algos, pk_algos_length)) < 0)
    {
      gnutls_assert ();
      return ret;
    }

  /* We should reply with a certificate message, 
   * even if we have no certificate to send.
   */
  session->key->crt_requested = 1;

  return 0;
}

int
_gnutls_gen_cert_client_crt_vrfy (gnutls_session_t session,
                                   gnutls_buffer_st * data)
{
  int ret;
  gnutls_pcert_st *apr_cert_list;
  gnutls_privkey_t apr_pkey;
  int apr_cert_list_length;
  gnutls_datum_t signature = { NULL, 0 };
  int total_data;
  gnutls_sign_algorithm_t sign_algo;
  gnutls_protocol_t ver = gnutls_protocol_get_version (session);

  /* find the appropriate certificate */
  if ((ret =
       _gnutls_get_selected_cert (session, &apr_cert_list,
                                  &apr_cert_list_length, &apr_pkey)) < 0)
    {
      gnutls_assert ();
      return ret;
    }

  if (apr_cert_list_length > 0)
    {
      if ((ret =
           _gnutls_handshake_sign_crt_vrfy (session,
                                             &apr_cert_list[0],
                                             apr_pkey, &signature)) < 0)
        {
          gnutls_assert ();
          return ret;
        }
      sign_algo = ret;
    }
  else
    {
      return 0;
    }

  total_data = signature.size + 2;

  /* add hash and signature algorithms */
  if (_gnutls_version_has_selectable_sighash (ver))
    {
      total_data += 2;
    }

  if (_gnutls_version_has_selectable_sighash (ver))
    {
      const sign_algorithm_st *aid;
      uint8_t p[2];
      /* error checking is not needed here since we have used those algorithms */
      aid = _gnutls_sign_to_tls_aid (sign_algo);
      if (aid == NULL)
        return gnutls_assert_val (GNUTLS_E_UNKNOWN_ALGORITHM);

      p[0] = aid->hash_algorithm;
      p[1] = aid->sign_algorithm;
      ret = _gnutls_buffer_append_data (data, p, 2);
      if (ret < 0)
        {
          gnutls_assert ();
          goto cleanup;
        }
    }

  ret =
    _gnutls_buffer_append_data_prefix (data, 16, signature.data,
                                       signature.size);
  if (ret < 0)
    {
      gnutls_assert ();
      goto cleanup;
    }

  ret = data->length;

cleanup:
  _gnutls_free_datum (&signature);
  return ret;
}

int
_gnutls_proc_cert_client_crt_vrfy (gnutls_session_t session,
                                    uint8_t * data, size_t data_size)
{
  int size, ret;
  ssize_t dsize = data_size;
  uint8_t *pdata = data;
  gnutls_datum_t sig;
  cert_auth_info_t info = _gnutls_get_auth_info (session);
  gnutls_pcert_st peer_cert;
  gnutls_sign_algorithm_t sign_algo = GNUTLS_SIGN_UNKNOWN;
  gnutls_protocol_t ver = gnutls_protocol_get_version (session);

  if (info == NULL || info->ncerts == 0)
    {
      gnutls_assert ();
      /* we need this in order to get peer's certificate */
      return GNUTLS_E_INTERNAL_ERROR;
    }

  if (_gnutls_version_has_selectable_sighash (ver))
    {
      sign_algorithm_st aid;

      DECR_LEN (dsize, 2);
      aid.hash_algorithm = pdata[0];
      aid.sign_algorithm = pdata[1];

      sign_algo = _gnutls_tls_aid_to_sign (&aid);
      if (sign_algo == GNUTLS_SIGN_UNKNOWN)
        {
          gnutls_assert ();
          return GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM;
        }
      pdata += 2;
    }

  ret = _gnutls_session_sign_algo_enabled (session, sign_algo);
  if (ret < 0)
    {
      gnutls_assert ();
      return GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM;
    }

  DECR_LEN (dsize, 2);
  size = _gnutls_read_uint16 (pdata);
  pdata += 2;

  DECR_LEN (dsize, size);

  sig.data = pdata;
  sig.size = size;

  ret = _gnutls_get_auth_info_pcert (&peer_cert,
                                     session->security_parameters.cert_type,
                                     info);

  if (ret < 0)
    {
      gnutls_assert ();
      return ret;
    }

  if ((ret =
       _gnutls_handshake_verify_crt_vrfy (session, &peer_cert, &sig,
                                           sign_algo)) < 0)
    {
      gnutls_assert ();
      gnutls_pcert_deinit (&peer_cert);
      return ret;
    }
  gnutls_pcert_deinit (&peer_cert);

  return 0;
}

int
_gnutls_gen_cert_server_cert_req (gnutls_session_t session,
                                  gnutls_buffer_st * data)
{
  gnutls_certificate_credentials_t cred;
  int size, ret;
  uint8_t tmp_data[CERTTYPE_SIZE];
  gnutls_protocol_t ver = gnutls_protocol_get_version (session);

  /* Now we need to generate the RDN sequence. This is
   * already in the CERTIFICATE_CRED structure, to improve
   * performance.
   */

  cred = (gnutls_certificate_credentials_t)
    _gnutls_get_cred (session->key, GNUTLS_CRD_CERTIFICATE, NULL);
  if (cred == NULL)
    {
      gnutls_assert ();
      return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
    }

  size = CERTTYPE_SIZE + 2;     /* 2 for gnutls_certificate_type_t + 2 for size of rdn_seq 
                                 */

  if (session->security_parameters.cert_type == GNUTLS_CRT_X509 &&
      session->internals.ignore_rdn_sequence == 0)
    size += cred->x509_rdn_sequence.size;

  if (_gnutls_version_has_selectable_sighash (ver))
    /* Need two bytes to announce the number of supported hash
       functions (see below).  */
    size += MAX_SIGN_ALGO_SIZE;

  tmp_data[0] = CERTTYPE_SIZE - 1;
  tmp_data[1] = RSA_SIGN;
  tmp_data[2] = DSA_SIGN;
  tmp_data[3] = ECDSA_SIGN;     /* only these for now */

  ret = _gnutls_buffer_append_data (data, tmp_data, CERTTYPE_SIZE);
  if (ret < 0)
    return gnutls_assert_val (ret);

  if (_gnutls_version_has_selectable_sighash (ver))
    {
      uint8_t p[MAX_SIGN_ALGO_SIZE];

      ret =
        _gnutls_sign_algorithm_write_params (session, p, MAX_SIGN_ALGO_SIZE);
      if (ret < 0)
        {
          gnutls_assert ();
          return ret;
        }

      /* recalculate size */
      size -= MAX_SIGN_ALGO_SIZE + ret;

      ret = _gnutls_buffer_append_data (data, p, ret);
      if (ret < 0)
        return gnutls_assert_val (ret);
    }

  if (session->security_parameters.cert_type == GNUTLS_CRT_X509 &&
      session->internals.ignore_rdn_sequence == 0)
    {
      ret =
        _gnutls_buffer_append_data_prefix (data, 16,
                                           cred->x509_rdn_sequence.data,
                                           cred->x509_rdn_sequence.size);
      if (ret < 0)
        return gnutls_assert_val (ret);
    }
  else
    {
      ret = _gnutls_buffer_append_prefix (data, 16, 0);
      if (ret < 0)
        return gnutls_assert_val (ret);
    }

  return data->length;
}


/* This function will return the appropriate certificate to use. 
 * Fills in the apr_cert_list, apr_cert_list_length and apr_pkey.
 * The return value is a negative error code on error.
 *
 * It is normal to return 0 with no certificates in client side.
 *
 */
int
_gnutls_get_selected_cert (gnutls_session_t session,
                           gnutls_pcert_st ** apr_cert_list,
                           int *apr_cert_list_length,
                           gnutls_privkey_t * apr_pkey)
{
  if (session->security_parameters.entity == GNUTLS_SERVER)
    {

      /* select_client_cert() has been called before.
       */

      *apr_cert_list = session->internals.selected_cert_list;
      *apr_pkey = session->internals.selected_key;
      *apr_cert_list_length = session->internals.selected_cert_list_length;

      if (*apr_cert_list_length == 0 || *apr_cert_list == NULL)
        {
          gnutls_assert ();
          return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
        }

    }
  else
    {                           /* CLIENT SIDE 
                                 */

      /* we have already decided which certificate
       * to send.
       */
      *apr_cert_list = session->internals.selected_cert_list;
      *apr_cert_list_length = session->internals.selected_cert_list_length;
      *apr_pkey = session->internals.selected_key;

    }

  return 0;
}

/* converts the given x509 certificate list to gnutls_pcert_st* and allocates
 * space for them.
 */
static gnutls_pcert_st *
alloc_and_load_x509_certs (gnutls_x509_crt_t * certs, unsigned ncerts)
{
  gnutls_pcert_st *local_certs;
  int ret = 0;
  unsigned i, j;

  if (certs == NULL)
    return NULL;

  local_certs = gnutls_malloc (sizeof (gnutls_pcert_st) * ncerts);
  if (local_certs == NULL)
    {
      gnutls_assert ();
      return NULL;
    }

  for (i = 0; i < ncerts; i++)
    {
      ret = gnutls_pcert_import_x509 (&local_certs[i], certs[i], 0);
      if (ret < 0)
        break;
    }

  if (ret < 0)
    {
      gnutls_assert ();
      for (j = 0; j < i; j++)
        {
          gnutls_pcert_deinit (&local_certs[j]);
        }
      gnutls_free (local_certs);
      return NULL;
    }

  return local_certs;
}

/* converts the given x509 key to gnutls_privkey* and allocates
 * space for it.
 */
static gnutls_privkey_t
alloc_and_load_x509_key (gnutls_x509_privkey_t key, int deinit)
{
  gnutls_privkey_t local_key;
  int ret = 0;

  if (key == NULL)
    return NULL;

  ret = gnutls_privkey_init (&local_key);
  if (ret < 0)
    {
      gnutls_assert ();
      return NULL;
    }

  ret =
    gnutls_privkey_import_x509 (local_key, key,
                                deinit ? GNUTLS_PRIVKEY_IMPORT_AUTO_RELEASE :
                                0);
  if (ret < 0)
    {
      gnutls_assert ();
      gnutls_privkey_deinit (local_key);
      return NULL;
    }

  return local_key;
}

/* converts the given pgp certificate to gnutls_cert* and allocates
 * space for them.
 */
#ifdef ENABLE_OPENPGP
static gnutls_pcert_st *
alloc_and_load_pgp_certs (gnutls_openpgp_crt_t cert)
{
  gnutls_pcert_st *local_certs;
  int ret = 0;

  if (cert == NULL)
    return NULL;

  local_certs = gnutls_malloc (sizeof (gnutls_pcert_st));
  if (local_certs == NULL)
    {
      gnutls_assert ();
      return NULL;
    }

  ret = gnutls_pcert_import_openpgp (local_certs, cert, 0);
  if (ret < 0)
    {
      gnutls_assert ();
      return NULL;
    }

  if (ret < 0)
    {
      gnutls_assert ();
      gnutls_pcert_deinit (local_certs);
      gnutls_free (local_certs);
      return NULL;
    }

  return local_certs;
}

/* converts the given raw key to gnutls_privkey* and allocates
 * space for it.
 */
static gnutls_privkey_t
alloc_and_load_pgp_key (gnutls_openpgp_privkey_t key, int deinit)
{
  gnutls_privkey_t local_key;
  int ret = 0;

  if (key == NULL)
    return NULL;

  ret = gnutls_privkey_init (&local_key);
  if (ret < 0)
    {
      gnutls_assert ();
      return NULL;
    }

  ret =
    gnutls_privkey_import_openpgp (local_key, key,
                                   deinit ? GNUTLS_PRIVKEY_IMPORT_AUTO_RELEASE
                                   : 0);
  if (ret < 0)
    {
      gnutls_assert ();
      gnutls_privkey_deinit (local_key);
      return NULL;
    }

  return local_key;
}
#endif

#ifdef ENABLE_PKCS11

/* converts the given raw key to gnutls_privkey* and allocates
 * space for it.
 */
static gnutls_privkey_t
alloc_and_load_pkcs11_key (gnutls_pkcs11_privkey_t key, int deinit)
{
  gnutls_privkey_t local_key;
  int ret = 0;

  if (key == NULL)
    return NULL;

  ret = gnutls_privkey_init (&local_key);
  if (ret < 0)
    {
      gnutls_assert ();
      return NULL;
    }

  ret =
    gnutls_privkey_import_pkcs11 (local_key, key,
                                  deinit ? GNUTLS_PRIVKEY_IMPORT_AUTO_RELEASE
                                  : 0);
  if (ret < 0)
    {
      gnutls_assert ();
      gnutls_privkey_deinit (local_key);
      return NULL;
    }

  return local_key;
}

#endif

void
_gnutls_selected_certs_deinit (gnutls_session_t session)
{
  if (session->internals.selected_need_free != 0)
    {
      int i;

      for (i = 0; i < session->internals.selected_cert_list_length; i++)
        {
          gnutls_pcert_deinit (&session->internals.selected_cert_list[i]);
        }
      gnutls_free (session->internals.selected_cert_list);
      session->internals.selected_cert_list = NULL;
      session->internals.selected_cert_list_length = 0;

      gnutls_privkey_deinit(session->internals.selected_key);
      session->internals.selected_key = NULL;
    }

  return;
}

void
_gnutls_selected_certs_set (gnutls_session_t session,
                            gnutls_pcert_st * certs, int ncerts,
                            gnutls_privkey_t key, int need_free)
{
  _gnutls_selected_certs_deinit (session);

  session->internals.selected_cert_list = certs;
  session->internals.selected_cert_list_length = ncerts;
  session->internals.selected_key = key;
  session->internals.selected_need_free = need_free;

}

static void get_server_name(gnutls_session_t session, uint8_t* name, size_t max_name_size)
{
int ret, i;
size_t max_name;
unsigned int type;

  ret = 0;
  for (i=0; !(ret<0);i++)
    {
      max_name = max_name_size;
      ret = gnutls_server_name_get (session, name, &max_name, &type, i);
      if (ret >= 0 && type == GNUTLS_NAME_DNS)
        return;
    }

  name[0] = 0;

  return;
}

/* finds the most appropriate certificate in the cert list.
 * The 'appropriate' is defined by the user.
 *
 * requested_algo holds the parameters required by the peer (RSA, DSA
 * or -1 for any).
 *
 * Returns 0 on success and a negative error code on error. The
 * selected certificate will be in session->internals.selected_*.
 *
 */
int
_gnutls_server_select_cert (gnutls_session_t session,
                            gnutls_pk_algorithm_t * pk_algos,
                            size_t pk_algos_size)
{
  unsigned i, j;
  int idx, ret;
  gnutls_certificate_credentials_t cred;
  char server_name[MAX_CN];

  cred = (gnutls_certificate_credentials_t)
    _gnutls_get_cred (session->key, GNUTLS_CRD_CERTIFICATE, NULL);
  if (cred == NULL)
    {
      gnutls_assert ();
      return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
    }

  /* If the callback which retrieves certificate has been set,
   * use it and leave.
   */
  if (cred->server_get_cert_callback || cred->get_cert_callback
      || cred->get_cert_callback2)
    {
      ret = call_get_cert_callback (session, NULL, 0, NULL, 0);
      if (ret < 0)
        return gnutls_assert_val (ret);
      return ret;
    }

  /* Otherwise... */
  
  get_server_name(session, (unsigned char*)server_name, sizeof(server_name));

  idx = -1;                     /* default is use no certificate */

  /* find certificates that match the requested server_name
   */
  
  if (server_name[0] != 0)
    {
      for (i = 0; i < cred->ncerts; i++)
        {
          if (cred->certs[i].names != NULL && _gnutls_str_array_match(cred->certs[i].names, server_name) != 0)
            {
              /* if requested algorithms are also compatible select it */
              gnutls_pk_algorithm pk =
                gnutls_pubkey_get_pk_algorithm (cred->certs[i].cert_list[0].pubkey,
                                            NULL);

              _gnutls_handshake_log("HSK[%p]: Requested server name: '%s', ctype: %s (%d)", session, server_name,
                    gnutls_certificate_type_get_name (session->security_parameters.cert_type), 
                    session->security_parameters.cert_type);

              if (session->security_parameters.cert_type == cred->certs[i].cert_list[0].type)
                {
                  for (j = 0; j < pk_algos_size; j++)
                  if (pk_algos[j] == pk)
                    {
		      idx = i;
	              goto finished;
	            }
                }
            }
        }
    }

  for (j = 0; j < pk_algos_size; j++)
    {
      _gnutls_handshake_log
        ("HSK[%p]: Requested PK algorithm: %s (%d) -- ctype: %s (%d)\n",
         session, gnutls_pk_get_name (pk_algos[j]), pk_algos[j],
         gnutls_certificate_type_get_name (session->security_parameters.
                                           cert_type),
         session->security_parameters.cert_type);

      for (i = 0; i < cred->ncerts; i++)
        {
          gnutls_pk_algorithm pk =
            gnutls_pubkey_get_pk_algorithm (cred->certs[i].cert_list[0].pubkey,
                                            NULL);
          /* find one compatible certificate
           */
          _gnutls_handshake_log
            ("HSK[%p]: certificate[%d] PK algorithm: %s (%d) - ctype: %s (%d)\n",
             session, i, gnutls_pk_get_name (pk), pk,
             gnutls_certificate_type_get_name (cred->certs[i].cert_list[0].type),
             cred->certs[i].cert_list[0].type);

          if (pk_algos[j] == pk)
            {
              /* if cert type matches
               */
	  /* *INDENT-OFF* */
	  if (session->security_parameters.cert_type == cred->certs[i].cert_list[0].type)
	    {
	      idx = i;
	      goto finished;
	    }
	  /* *INDENT-ON* */
            }
        }
    }

  /* store the certificate pointer for future use, in the handshake.
   * (This will allow not calling this callback again.)
   */
finished:
  if (idx >= 0)
    {
      _gnutls_selected_certs_set (session,
                                  &cred->certs[idx].cert_list[0],
                                  cred->certs[idx].cert_list_length,
                                  cred->pkey[idx], 0);
    }
  else
    {
      gnutls_assert ();
      /* Certificate does not support REQUESTED_ALGO.  */
      return GNUTLS_E_INSUFFICIENT_CREDENTIALS;
    }

  return 0;
}

/* Frees the rsa_info_st structure.
 */
void
_gnutls_free_rsa_info (rsa_info_st * rsa)
{
  _gnutls_free_datum (&rsa->modulus);
  _gnutls_free_datum (&rsa->exponent);
}
